package dev.ece.suit.loader.models.fbx;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

import dev.ece.util.io.LittleEndianInputStream;
import dev.ece.util.lib.Matrix4f;
import dev.ece.util.lib.Vector2f;
import dev.ece.util.lib.Vector3f;

public class FBXProperty {

	private char type;
	
	private Object value;
	
	private int encoding;
	
	private int length;
	
	private FBXProperty() {}
	
	public static FBXProperty loadFBXProperty(LittleEndianInputStream input) throws IOException {
		FBXProperty property = new FBXProperty();
		property.type = input.readUnsignedChar();
		switch(property.type) {
			case 'S':
				property.value = input.readString(input.readInt());
				break;
			case 'Y': input.skip(2); break;
			case 'C': 
				property.value = input.readUnsignedChar(); 
				break;
			case 'I': 
				property.value = input.readInt();
				break;
			case 'F': 
				property.value = input.readFloat(); 
				break;
			case 'D': 
				property.value = input.readDouble();  
				break;
			case 'L': 
				property.value = input.readLong(); 
				break;
			case 'R':
				input.skip(input.readInt());
				break;
			case 'b':
			case 'f':
			case 'd':
			case 'l':
			case 'i':
				//length
				property.length = input.readInt();
				//encoding;
				property.encoding = input.readInt();
				int comp_len = input.readInt();
				byte[] buffer = new byte[comp_len];
				property.value = buffer;
				input.read(buffer);
				break;
			default:
				throw new IllegalMonitorStateException("未知属性类型.");
		}
		return property;
	}

	public int[] toIndice() throws IOException {
		return toIndice(false);
	}
	
	public int[] toIndice(boolean noneg) throws IOException {
		if((this.type == 'i' || this.type == 'l') && this.value instanceof byte[]) {
			byte[] buffer = (byte[]) this.value;
			int[] result = new int[this.length];
			LittleEndianInputStream input = null;
			boolean isLong = this.type == 'l';
			if(this.encoding == 1) {
				input = new LittleEndianInputStream(new InflaterInputStream(new ByteArrayInputStream(buffer), new Inflater(), buffer.length));
			} else {
				input = new LittleEndianInputStream(new ByteArrayInputStream(buffer));
			}
			try {
				for(int i=0; i<result.length; i++) {
					if(isLong) {
						result[i] = (int) input.readLong();
					} else {
						result[i] = input.readInt();
					}
					if(noneg && result[i] < 0) {
						result[i] = -result[i] - 1;
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			return result;
		}
		return null;
	}
	
	public long[] toLongArray() throws IOException {
		if(this.type == 'l' && this.value instanceof byte[]) {
			byte[] buffer = (byte[]) this.value;
			long[] result = new long[this.length];
			LittleEndianInputStream input = null;
			if(this.encoding == 1) {
				input = new LittleEndianInputStream(new InflaterInputStream(new ByteArrayInputStream(buffer), new Inflater(), buffer.length));
			} else {
				input = new LittleEndianInputStream(new ByteArrayInputStream(buffer));
			}
			try {
				for(int i=0; i<result.length; i++) {
					result[i] = input.readLong();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			return result;
		}
		return null;
	}
	
	public float[] toFloatArray() throws IOException {
		if((this.type == 'd' || this.type == 'f') && this.value instanceof byte[]) {
			byte[] buffer = (byte[]) this.value;
			float[] result = new float[this.length];
			LittleEndianInputStream input = null;
			boolean isDouble = this.type == 'd';
			if(this.encoding == 1) {
				input = new LittleEndianInputStream(new InflaterInputStream(new ByteArrayInputStream(buffer), new Inflater(), buffer.length));
			} else {
				input = new LittleEndianInputStream(new ByteArrayInputStream(buffer));
			}
			try {
				for(int i=0; i<result.length; i++) {
					if(isDouble) {
						result[i] = (float) input.readDouble();
					} else {
						result[i] = input.readFloat();
					}
					if(Float.isNaN(result[i])) {
						result[i] = 0;
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			return result;
		}
		return null;
	}
	
	public Matrix4f toMatrix4f() throws IOException {
//		float[] matrix = new float[16];
//		float[] array = toFloatArray();
//		int index = 0;
//		for(int i=0; i<4; i++) {
//			matrix[i] = array[index ++];
//			matrix[4 + i] = array[index ++];
//			matrix[8 + i] = array[index ++];
//			matrix[12 + i] = array[index ++];
//		}
		return new Matrix4f(toFloatArray());
	}
	
	public Vector2f[] toVector2f() throws IOException {
		if((this.type == 'd' || this.type == 'f') && this.value instanceof byte[]) {
			Vector2f[] result = new Vector2f[this.length / 2];
			byte[] buffer = (byte[]) this.value;
			boolean isDouble = this.type == 'd';
			LittleEndianInputStream input = null;
			if(this.encoding == 1) {
				input = new LittleEndianInputStream(new InflaterInputStream(new ByteArrayInputStream(buffer), new Inflater(), buffer.length));
			} else {
				input = new LittleEndianInputStream(new ByteArrayInputStream(buffer));
			}
			try {
				for(int i=0; i<result.length; i++) {
					if(isDouble) {
						result[i] = new Vector2f(
								(float) input.readDouble(), 
								1f - (float) input.readDouble());
					} else {
						result[i] = new Vector2f(
								input.readFloat(), 
								1f - input.readFloat());
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			return result;
		}
		return null;
	}
	
	public Vector3f[] toVector3f() throws IOException {
		if((this.type == 'd' || this.type == 'f') && this.value instanceof byte[]) {
			Vector3f[] result = new Vector3f[this.length / 3];
			byte[] buffer = (byte[]) this.value;
			boolean isDouble = this.type == 'd';
			LittleEndianInputStream input = null;
			if(this.encoding == 1) {
				input = new LittleEndianInputStream(new InflaterInputStream(new ByteArrayInputStream(buffer), new Inflater(), buffer.length));
			} else {
				input = new LittleEndianInputStream(new ByteArrayInputStream(buffer));
			}
			try {
				for(int i=0; i<result.length; i++) {
					if(isDouble) {
						result[i] = new Vector3f(
								(float) input.readDouble(), 
								(float) input.readDouble(), 
								(float) input.readDouble());
					} else {
						result[i] = new Vector3f(
								input.readFloat(), 
								input.readFloat(), 
								input.readFloat());
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			return result;
		}
		return null;
	}
	
	public char getType() {
		return type;
	}

	public Object getValue() {
		return value;
	}

	public int getEncoding() {
		return encoding;
	}

	public int getLength() {
		return length;
	}

	@Override
	public String toString() {
		return new StringBuilder()
				.append("(type=").append(this.type)
				.append(",value=").append(this.value)
				.append(")").toString();
	}
	
}
